/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.sunspotworld;

import com.sun.spot.io.j2me.radiogram.RadiogramConnection;
import com.sun.spot.peripheral.radio.RadioFactory;
import com.sun.spot.sensorboard.EDemoBoard;
import com.sun.spot.sensorboard.peripheral.ITriColorLED;
import com.sun.spot.util.IEEEAddress;
import com.sun.spot.util.Utils;
import com.sun.squawk.util.StringTokenizer;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Random;
import java.util.Vector;
import javax.microedition.io.Connector;
import javax.microedition.io.Datagram;
import javax.microedition.io.DatagramConnection;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

/**
 *
 * @author seb
 */
public class Main extends MIDlet implements Protocoll{

    //DEBUG
    private static final boolean DEBUG = true;
    //!DEBUG

    private Vector connections;
    private Vector idleConnections;
    private Vector usedPorts;
    private Game game;
    private DatagramConnection dgBConn;                       //Used for broadcast
    //private RadiogramConnection radioCon;
    private static int MIN_CHANNEL = 43;
    private static int GENERATED_PORTS = 10;
    private long thisAddressAsNumber;
    private Thread broadcastThread;
    private int trys = 0;
    private boolean broadcasting = true;
    private Hashtable runningThreads;
    private Hashtable idleThreads;

    protected void startApp() throws MIDletStateChangeException {
        System.out.println("Hi, guys!");
        
    }

    public Main(){
        connections = new Vector();
        createGame(this);
        idleConnections = new Vector();
        usedPorts = new Vector();
        runningThreads = new Hashtable(GENERATED_PORTS);
        idleThreads = new Hashtable(GENERATED_PORTS);

        try {
            dgBConn = (RadiogramConnection) Connector.open("radiogram://broadcast:37");

            if(DEBUG){
                System.out.println("RadioCon opend");
            }

            thisAddressAsNumber = RadioFactory.getRadioPolicyManager().getIEEEAddress();
            broadcastThread = new BroadcastReceiveThread(this);
            broadcastThread.start();

        } catch (IOException ex) {
            ex.printStackTrace();
        }

        while(broadcasting){
            String message = INVITE +" ";
            int[] ports = generateAvailablePortNumbers(GENERATED_PORTS);

            for(int i = 0; i< GENERATED_PORTS; i++){
                message += ports[i]+" ";
            }

            sendBroadcastMessage(message);
            //  System.out.println("Sending invite");
            //massMessage("message!");
            Utils.sleep(100);
            }
        
    }

    public void createGame(Main main){

        game = new Game(this);

    }

    public void setBroadcasting(boolean value){
        this.broadcasting = value;
    }
    private synchronized void addConnection(String adress, int port) throws IOException{

//        for(int i=0; i<connections.size(); i++){
//            Connection con = (Connection)connections.elementAt(i);
//
//            if(con.getAdress() == adress){
//                return con;
//            }
//       }

       Connection con = new Connection(adress, port);

       if(!connections.contains((Object)con)){
           connections.addElement((Object) con);
           RadioStreamReceiveThread t = new RadioStreamReceiveThread(this, con);
           t.start();
           runningThreads.put((Object)new Long(IEEEAddress.toLong(adress)), (Object)t);
       }else{
           System.err.println("Error @ Main.java:addConnection() - Connection is already in the vector!");
       }
       game.prepareForGame();
       game.setConnectLed();
       // return con;
    }

    private void idleConnection(long adress){
        Connection con = getConnectionByAdress(adress);
       // RadioStreamReceiveThread t = (RadioStreamReceiveThread) runningThreads.get((Object)new Long(adress));

         if(con != null){
            connections.removeElement((Object)con);
            idleConnections.addElement((Object)con);
           // runningThreads.remove((Object)new Long(adress));
            //idleThreads.put((Object)new Long(adress), (Object)t);
         }else{
            System.err.println("Error @ Main.java:idleConnection() - Connection is null!");
        }
    }

    private void enableConnection(long adress){
        Connection con = getIdleConnectionByAdress(adress);
       // RadioStreamReceiveThread t = (RadioStreamReceiveThread) runningThreads.get((Object)new Long(adress));

        if(con != null){
            idleConnections.removeElement((Object)con);
            connections.addElement((Object)con);
        }else{
            System.err.println("Error @ Main.java:enableConnection() - Connection is null!");
        }

    }

    /**
     * DO NOT USE THIS!!!!!!
     * @param adress
     */
    /*
    private void removeConnection(long adress){
        Connection con = getConnectionByAdress(adress);
        RadioStreamReceiveThread t = (RadioStreamReceiveThread) runningThreads.get((Object)new Long(adress));

       if(con != null){
//           t.stopThread();  // kill the thread
//           t.interrupt();
//
//           // con.close();    // kill the connection
//            connections.removeElement((Object) con);
        }else{
            System.err.println("Error @ Main.java:removeConnection() - Connection is null!");
        }
    }
*/
    
    public Connection getConnectionByAdress(long adress){

        for(int i=0; i<connections.size(); i++){
            Connection con = (Connection)connections.elementAt(i);

            if(IEEEAddress.toLong(con.getAdress()) == adress){
                return con;
            }
        }

      //  System.err.println("Error @ Main.java:getConnectionByAdress - No connection found!");
        return null;
    }

     public Connection getIdleConnectionByAdress(long adress){
         for(int i=0; i<idleConnections.size(); i++){
            Connection con = (Connection)idleConnections.elementAt(i);

              if(IEEEAddress.toLong(con.getAdress()) == adress){
                return con;
            }
        }

      //  System.err.println("Error @ Main.java:getIdleConnectionByAdress - No connection found!");
        return null;
     }


    /**
     * Returns the ports of all connections
     * @return Array of ports
     */
    private int[] getPorts(){
        int[] ports = new int[connections.size()-1];

        for(int i=0; i<connections.size(); i++){
            Connection con = (Connection)connections.elementAt(i);
            ports[i]=con.getPort();
        }
        return ports;
    }

    private synchronized void sendBroadcastMessage(String message){
//       if(dgBConn == null) try {
//            dgBConn = (DatagramConnection) Connector.open("radiogram://broadcast:37");
//        } catch (IOException ex) {
//            System.err.println("Error @ Main.java:sendBroadcastMessage - Could not open radiogram broadcast connection");
//            ex.printStackTrace();
//        }

       try{
           Datagram dg = dgBConn.newDatagram(dgBConn.getMaximumLength());
            dg.writeUTF(message);
            dgBConn.send(dg);
       }catch (IOException ex){
           System.err.println("Error @ Main.java:sendBroadcastMessage - Could not send datagram");
           ex.printStackTrace();
       }

    }

    public synchronized void sendConnectionMessage(String message, long adress){

        Connection con = getConnectionByAdress(adress);

        if(con != null){
         //   try {
//                con.writeUTF(message);
//                con.flush();
                con.sendMessage(message);
        //    } catch (IOException ex) {
        //        System.err.println("Error @ Main.java:sendConnectionMessage - Could not write to connection!");
        //        ex.printStackTrace();
          //  }
        } else {
            System.err.println("Error @ Main.java:sendConnectionMessage - Connection is null!");
        }
    }

    public synchronized  void massMessage(String message){
//        if(connections.size()>0){
//             for(int i=0; i<connections.size(); i++){
//                Connection con = (Connection)connections.elementAt(i);
//               // try {
//                //   System.out.println("MassMessage:" + con.toString() + " " + con.getAdress() + " " + con.getPortNumber());
//                   System.out.println("size of connections: " + connections.size());
//
//               //     con.writeUTF(message);
//                 //   con.flush();
//                   con.sendMessage(message);
//                    //Utils.sleep(100);
////                } catch (IOException ex) {
////                    ex.printStackTrace();
////                }
//             }

        // broadcast that shit
            sendBroadcastMessage(message);
    }

     public synchronized  void receiveConnectionMessage(String message, String adress){
         if(DEBUG){
            System.out.println("Message received: " + message);
         }
        if(game != null){
            String[] parts = separateStrings(message);
            if(parts[0].length() > 2){
                game.receiveMessage(message);
            } else {
            if(Integer.parseInt(parts[0]) == DEADPLAYER){
                idleConnection(IEEEAddress.toLong(adress));
                game.resetForRound();
            }
            if(Integer.parseInt(parts[0]) == STARTGAME){
                game.gameStarted();
            }
            }
        }


     }

     private void replyWithDatagram(String message, Datagram adressSourceDG, RadiogramConnection rgCon){
        Datagram dgreply;

        try {
            
                dgreply = rgCon.newDatagram(rgCon.getMaximumLength());
        } catch (IOException ex) {
            ex.printStackTrace();
            return;
        }
        
        try{
                dgreply.reset();        // reset stream pointer
              //  System.out.println("------------------------> Replyadress: " + adressSourceDG.getAddress());
                dgreply.setAddress(adressSourceDG.getAddress());
                

                dgreply.writeUTF(message);
               
                rgCon.send(dgreply);
            
        } catch (IOException ex) {
//            System.out.println("----------Exception caught!");
//            if(trys>=10){
//            ex.printStackTrace();
//            }else{
                System.out.println("----------Retrying...");
                replyWithDatagram(message,adressSourceDG,rgCon);
                
//            }

        }

    }


     public synchronized  void recieveBroadcastMessage(Datagram datagram, RadiogramConnection rgCon){
         String message;
         int port = -1;
         String otherAddress =  datagram.getAddress();
         long otherAddressAsNumber = IEEEAddress.toLong(otherAddress);
         boolean accepted = false;

        try {
            message = datagram.readUTF();
           // System.out.println(message);
             String messageTokens[] = separateStrings(message);

            
    System.out.println(message);
                switch(Integer.parseInt(messageTokens[0])){

                    ////////////////////////////////////
                    ///
                    ///  RECEIVER
                    ///
                    ////////////////////////////////////
                    // Got invite to the game
                    case(INVITE):
                        if(getConnectionByAdress(IEEEAddress.toLong(datagram.getAddress())) == null){
                         //      && (getConnectionByAdress(IEEEAddress.toLong(datagram.getAddress())) == null) )){
                                int ports[] = new int[GENERATED_PORTS];

                                for(int i = 1; i<GENERATED_PORTS; i++){
                                    ports[i-1]=Integer.parseInt(messageTokens[i]);
                                }

                                port = selectPortFrom(ports);
                               // System.out.println("--> Got invite!");
                                replyWithDatagram(ACCEPTINVITE + " " + port, datagram, rgCon);
                        }
                    break;

                    ////////////////////////////////////
                    ///
                    ///  SENDER
                    ///
                    ////////////////////////////////////
                    // Got confirmation of the invitation
                    case(ACCEPTINVITE):
                         //      && (getConnectionByAdress(IEEEAddress.toLong(datagram.getAddress())) == null))){
                         if(getConnectionByAdress(IEEEAddress.toLong(datagram.getAddress())) == null){
                                port = Integer.valueOf(messageTokens[1]).intValue();

                                // new connection
                                if(!usedPorts.contains( new Integer(port) ) ){

                                    usedPorts.addElement(new Integer(port));
                                    if(DEBUG){
                                         System.out.println("--> Send confirm!");
                                    }
                                    replyWithDatagram(CONFIRMINVITE +" " + port, datagram, rgCon);
                                    accepted = true;

//                                } // connection allready in vector
//                                else if(getConnectionByAdress(IEEEAddress.toLong(datagram.getAddress())) != null
//                                        && connections.contains((Object) getConnectionByAdress(IEEEAddress.toLong(datagram.getAddress())))){
//
//                                    int portnummer= getConnectionByAdress(IEEEAddress.toLong(datagram.getAddress())).getPortNumber();
//                                    replyWithDatagram(CONFIRMINVITE +" " + portnummer, datagram, rgCon);
//                                     accepted=false;

                                } // kill connection
                                else{
                                    // somebody uses the same port number! Cancel and begin again...
                                    replyWithDatagram(CANCEL_CONNECTION+"", datagram, rgCon);
                                    accepted=false;
                                }
                         }
                    break;

                    ////////////////////////////////////
                    ///
                    ///  RECEIVER
                    ///
                    ////////////////////////////////////
                    // Got confirmation of the confirmation -> Connection accepted!
                    case(CONFIRMINVITE):
                        if(getConnectionByAdress(IEEEAddress.toLong(datagram.getAddress())) == null){
                          //   && (getConnectionByAdress(IEEEAddress.toLong(datagram.getAddress())) == null)){
                                if(DEBUG){
                                    System.out.println("--> Got confirm!");
                                }
                                port = Integer.valueOf(messageTokens[1]).intValue();
                                usedPorts.addElement(new Integer(port));
                                accepted = true;
                        }
                    break;

                    ////////////////////////////////////
                    ///
                    ///  RECEIVER
                    ///
                    ////////////////////////////////////
                    // Connection canceld
                    case(CANCEL_CONNECTION):
                        if(getConnectionByAdress(IEEEAddress.toLong(datagram.getAddress())) == null){
                        //       && (getConnectionByAdress(IEEEAddress.toLong(datagram.getAddress())) == null)){

                                 if(DEBUG){
                                    System.out.println("X--X Connection killed by server... reconnecting");
                                }
                                 accepted = false;
                        }
                    break;

                    case(STARTGAME):
                         if(DEBUG){
                            System.out.println("Game started");
                         }
                         game.receiveMessage(message);
                         
                    break;

                    default:
                        
                }
                // Handshake okay? Save the connection
                        if(accepted){
                                if(DEBUG){
                                    System.out.println("<--> Connection accepted! " + otherAddressAsNumber +":"+port);
                                }

                                //dont double entrys
                              //  if(!(getConnectionByAdress(IEEEAddress.toLong(datagram.getAddress())) == null)){
                                    addConnection(IEEEAddress.toDottedHex(otherAddressAsNumber), port);
                               // }
                        }

        } catch (IOException ex) {
            ex.printStackTrace();
        }
     }

      private int[] generateAvailablePortNumbers(int total){
        int[] r = new int[total];
        Vector used = new Vector();

        // Note in Java's &&, right hand expression is not evaluated unless left is true.

        for(int i=0; i<connections.size(); i++){
            Connection con = (Connection) connections.elementAt(i);
            if( con != null ){
                used.addElement(new Integer( con.getPort()));
            }
        }
        //Start at the lowest channel, and search upwards till an available one is found.

        int c = MIN_CHANNEL;
        int index = 0;
        while(index < total){
            if (! used.contains(new Integer(c))){
                r[index] = c;
                index = index + 1;
            }
            c = c + 1;
        }
       // generateCounter++;
        return r;
    }

       private int selectPortFrom(int[] array) {

        Vector pieces = new Vector();
        for(int i = 0; i<array.length; i++){
            pieces.addElement(array[i]+"");
        }

        String pn;

        for(int i=0; i<connections.size(); i++){
            Connection con = (Connection) connections.elementAt(i);

            if(con != null) {
                pn = "" +  con.getPort();

                if(pieces.contains(pn)){
                    pieces.removeElement(pn);
                }
            }
        }
        // return a random port
        Random r = new Random();
        return Integer.parseInt( (String)( pieces.elementAt( r.nextInt( pieces.size()-1 ) ) ) );
    }

     public Vector getConnections(){
         return connections;
     }

    private String[] separateStrings(String msg) {
        StringTokenizer stk = new StringTokenizer(msg, " ");
        String [] result = new String[stk.countTokens()];
        for (int i = 0; stk.hasMoreTokens(); i++) {
            result[i] = stk.nextToken();
            }
        return result;
    }

    protected void destroyApp(boolean unconditional) throws MIDletStateChangeException {

        ITriColorLED[] leds = EDemoBoard.getInstance().getLEDs();
        for (int i = 0; i < 8; i++){
            leds[i].setOff();       // turn off all LEDs
        }
        System.out.println("Application shut down!");
    }

    protected void pauseApp() {
        //no!
    }
}